
#include <cassert>
#include <cstring>
#include <errno.h>

#include "Composition.h"
#include "LogUtil.h"

__BEGIN_NAMESPACE_DECL_

// Constructor
Composition::Composition()
    : bpm_(0), numNotes_(0), currentNote_(0),
      looped_(false), notesSize_(0), notes_(NULL)
{
}

// Destructor
Composition::~Composition()
{
    clear();
}

void
Composition::clear()
{
    // Clear the list of notes we currently have.
    if (numNotes_ > 0 && notes_ != NULL)
    {
        free(notes_);
        notes_ = NULL;
        numNotes_ = 0;
        notesSize_ = 0;
        looped_ = false;
        bpm_ = 0;
        currentNote_ = 0;
        title_ = "";
    }
}

bool 
Composition::loadFromFile (const char *filename)
{
    FILE *fp;
#define BUF_SIZE 512
    char line[BUF_SIZE];
    char instruments[BUF_SIZE];
    char title[32];
    unsigned int bpm, loop, noteType;

    assert (filename != NULL);

    if ((fp = fopen(filename, "r")) == NULL)
    {
        ERROR << "Unable to open file \"" << filename << "\": " << strerror(errno);
        return false;
    }

    clear();

    while (fgets(line, BUF_SIZE, fp) != NULL)
    {
        // skip empty lines and comments
        if (line[0] == '\0' || line[0] == '#' || 
            line[0] == '\n' || line[0] == '\r')
            continue;

        if (sscanf(line, "BPM=%u", &bpm) == 1)
        {
            if (!setBPM(bpm))
            {
                ERROR << "Unable to set BPM.  Failing...";
                return false;
            }
            DEBUG << "BPM set to " << bpm;
        }
        else if (sscanf(line, "Loop=%u", &loop) == 1)
        {
            setLooped((loop == 1) ? true : false);
            if (isLooped())
            {
                DEBUG << "Composition set to looped";
            }
        }
        else if (sscanf(line, "Title=%30[_-A-Za-z0-9 ]", title) == 1)
        {
            title[31]='\0';
            setTitle(title);
        }
        else if (sscanf(line, "%u %8s", &noteType, instruments) == 2)
        {
            if (noteType == 1)
                addNote (getDurationWhole(), instruments);
            else if (noteType == 2)
                addNote (getDurationHalf(), instruments);
            else if (noteType == 4)
                addNote (getDurationQuarter(), instruments);
            else if (noteType == 8)
                addNote (getDurationEighth(), instruments);
            else if (noteType == 16)
                addNote (getDurationSixteenth(), instruments);
            else
            {
                WARN << "Unknown note type " << noteType << ", skipping...";
                continue;
            }
        }
        else
        {
            WARN << "Unknown directive \"" << line << "\", skipping...";
            continue;
        }
    }
    
    return true;
}

size_t 
Composition::getBPM ()
{
    return bpm_;
}

bool
Composition::setBPM(size_t bpm)
{
    if (bpm == 0 || bpm > 300)
    {
        ERROR << "Refusing to set BPM to " << bpm << " (must be between 1 and 300)";
        return false;
    }

    bpm_ = bpm;

    return true;
}

bool
Composition::isLooped() const
{
    return looped_;
}

void 
Composition::setLooped(bool looped)
{
    looped_ = looped;
}

const char *
Composition::getTitle() const
{
    return title_.c_str();
}

void 
Composition::setTitle (const char *title)
{
    if (title != NULL)
        title_ = title;
}


bool
Composition::getCurrentNote(Note& note)
{
    if (numNotes_ == 0 || currentNote_ > numNotes_)
        return false;

    note = notes_[currentNote_];
    return true;
}

void
Composition::reset()
{
    currentNote_ = 0;
}

void
Composition::next()
{
    currentNote_++;
    if (isLooped() && currentNote_ >= numNotes_)
        currentNote_ = 0;
}

bool
Composition::isPlaying()
{
    if (currentNote_ >= numNotes_)
        return false;

    return true;
}

double Composition::getDurationSixteenth() const { return getDurationQuarter()/4; }
double Composition::getDurationEighth()    const { return getDurationQuarter()/2; }
double Composition::getDurationQuarter()   const { return (double)60/bpm_; }
double Composition::getDurationHalf()      const { return getDurationQuarter()*2; }
double Composition::getDurationWhole()     const { return getDurationQuarter()*4; }

void
Composition::addNote (double duration, const char *instruments)
{
    Note note;

    DEBUG << "Adding note duration= " << duration << " and instruments=" << instruments;

    note.duration = duration;
    note.instruments = 0;
    if (strchr(instruments, 'H') != NULL)
        note.instruments |= NOTE_HIHAT;
    if (strchr(instruments, 'S') != NULL)
        note.instruments |= NOTE_SNARE;
    if (strchr(instruments, 'K') != NULL)
        note.instruments |= NOTE_KICK;
    if (strchr(instruments, 'C') != NULL)
        note.instruments |= NOTE_CRASH;
    if (strchr(instruments, 't') != NULL)
        note.instruments |= NOTE_TOM12;
    if (strchr(instruments, 'T') != NULL)
        note.instruments |= NOTE_TOM13;
    if (strchr(instruments, 'F') != NULL)
        note.instruments |= NOTE_TOM16;
    if (strchr(instruments, 'R') != NULL)
        note.instruments |= NOTE_RIDE;

    addNote(note);
}

void
Composition::addNote (const Note& note)
{
#define NOTES_SIZE_INC 10

    numNotes_++;
    if (numNotes_ > notesSize_)
    {
        notesSize_ += NOTES_SIZE_INC;
        notes_ = (Note *)realloc (notes_, sizeof(Note) * notesSize_);
    }

    notes_[numNotes_-1] = note;

    return;
}

__END_NAMESPACE_DECL_

